import {
  AnimationAction,
  AnimationMixer,
  Clock,
  DoubleSide,
  Group,
  Material,
  Mesh,
  MeshStandardMaterial,
  Object3D,
  Vector3,
} from "three";
import threeUtils from "./threeUtils";
import threeLoader from "./threeLoader";
import * as SkeletonUtils from "three/examples/jsm/utils/SkeletonUtils";
import { get, includes, isString } from "lodash";

// 模型基类
export default class MeshBase extends Group {
  constructor(url?: string) {
    super();
    url && this.loadModel(url);
  }

  url = "";

  // 模型对象
  static models: Map<string, Object3D> = new Map();

  // 模型是否在加载中
  static loadModeling: Map<string, boolean> = new Map();

  // 加载完成
  loaded = false;

  // 是否有动画
  hasAnimation = false;

  //动画混合器
  mixer?: AnimationMixer;

  //动画剪辑对象数组
  animationActions: { [key: string]: AnimationAction } = {};

  // 获取文件名的后缀
  getSuffix(filename: string) {
    const match = filename.match(/\.([^\.]+)$/);
    return match ? match[1].toLowerCase() : "";
  }

  // 重置子元素
  reset(...object: Object3D[]) {
    threeUtils.dipose(this);
    super.clear();
    super.add(...object);
    return this;
  }

  // 如果url 是blob地址的话,就用suffix字段来制定模型的类型
  async loadModel(url: string | File | Array<File>, filename: string = "") {
    //获取模型文件的地址
    const modelurl = await (async () => {
      if (isString(url)) {
        return url;
      } else if (url instanceof File) {
        return url.name;
      } else if (url instanceof Array) {
        let modelurl = "";
        const suffixs = [
          "zip",
          "obj",
          "glb",
          "gltf",
          "fbx",
          "dae",
          "stl",
          "usdz",
        ];
        for (let file of url) {
          if (suffixs.includes(threeUtils.getSuffix(file.name))) {
            modelurl = file.name;
          }
        }
        return modelurl;
      }
    })();

    this.url = modelurl || "";

    if (!modelurl) {
      return;
    }

    this.loaded = false;

    // 没有模型并且没有加载任务时就去加载模型
    if (
      !MeshBase.models.get(modelurl) &&
      !MeshBase.loadModeling.get(modelurl)
    ) {
      MeshBase.loadModeling.set(modelurl, true);

      // 万能加载器
      let modal = await threeLoader.loadModel(url, filename);

      MeshBase.models.set(modelurl, modal);
      MeshBase.loadModeling.set(modelurl, false);
    }

    // 如果模型还在加载中, 就等待模型加载完成
    do {
      await new Promise((resolve) => setTimeout(resolve, 50));
    } while (MeshBase.loadModeling.get(modelurl));

    if (MeshBase.models.get(modelurl)) {
      const mesh = MeshBase.models.get(modelurl)!;
      // 判断是否有动画
      this.hasAnimation = mesh.animations.length > 0;

      if (this.hasAnimation) {
        const model = SkeletonUtils.clone(mesh);
        this.mixer = new AnimationMixer(model);

        this.animationActions = {};
        mesh.animations.map((animation) => {
          const animationAction = this.mixer!.clipAction(animation).play();
          animationAction.weight = 0;
          this.animationActions[animation.name] = animationAction;
        });

        this.reset(model);
      } else {
        this.reset(mesh.clone(true));
      }
    }

    await new Promise((resolve) => requestAnimationFrame(resolve));

    this.loaded = true;

    // 加载完成事件
    this.dispatchEvent({ type: "loaded" } as never);
  }

  // 超出绘制区域时不绘制
  frustumCulled = false;

  // 创建阴影
  castShadow: boolean = false;

  // 接受阴影
  receiveShadow: boolean = false;

  // promise.resole 队列
  resoles: Array<(value: void | PromiseLike<void>) => void> = [];
  // 等待加载加载完成
  async awaitLoaded() {
    if (!this.loaded) {
      await new Promise<void>((resolve) => {
        if (this.resoles.length === 0) {
          (async () => {
            while (!this.loaded) {
              await new Promise((resolve) => requestAnimationFrame(resolve));
            }
            while (this.resoles.length > 0) {
              const resole = this.resoles.pop();
              resole && resole();
            }
          })();
        }
        this.resoles.unshift(resolve);
      });
    }
  }

  //遍历模型
  async traverseMesh(callback: (mesh: Mesh) => void) {
    // 等待加载完成
    await this.awaitLoaded();

    this.traverse((obj) => {
      const mesh = obj as Mesh;
      if (mesh.isMesh) {
        callback(mesh);
      }
    });
  }

  clock = new Clock();

  //设置动画的权重
  async setClipActionWeight(weights: { [key: string]: number } = {}) {
    // 等待加载完成
    await this.awaitLoaded();
    for (let key in this.animationActions) {
      this.animationActions[key].weight = weights[key] || 0;
    }
  }

  //获取动画列表
  getAnimas() {
    return Object.keys(this.animationActions);
  }

  //播放指定动画
  setAnimas(anima: string = "") {
    this.setClipActionWeight({ [anima]: 1 });
  }

  //停止所有动画
  stop() {
    this.setClipActionWeight();
  }

  updateMatrix(): void {
    //播放动画
    if (this.mixer) {
      this.mixer.update(this.clock.getDelta());
    }
    // 深度应用属性
    this.traverse((obj) => {
      const mesh = obj as Mesh;
      if (mesh.isMesh) {
        mesh.frustumCulled = this.frustumCulled;
        mesh.castShadow = this.castShadow;
        mesh.receiveShadow = this.receiveShadow;
      }
    });

    super.updateMatrix();
  }

  // 获取模型的尺寸
  async getSize() {
    await this.awaitLoaded();
    return threeUtils.getSize(this);
  }

  // 居中
  async center() {
    await this.awaitLoaded();
    threeUtils.center(this);
  }

  //底对齐
  async alignBottom(y: number = 0) {
    await this.awaitLoaded();
    threeUtils.alignBottom(this, y);
  }

  // 放大到指定尺寸
  async scaleTo(
    size: number = 1,
    key: Array<"x" | "y" | "z"> = ["x", "y", "z"]
  ) {
    await this.awaitLoaded();
    threeUtils.scaleTo(this, size, key);
  }

  //按照法线拓展
  expandBynormal(length: number) {
    this.traverseMesh((mesh) => {
      // 没有法线就计算法线
      mesh.geometry = mesh.geometry.clone();
      if (!mesh.geometry.getAttribute("normal")) {
        mesh.geometry.computeVertexNormals();
      }

      const normal = mesh.geometry.getAttribute("normal");
      const position = mesh.geometry.getAttribute("position");
      const v3 = new Vector3(0, 0, 0);
      for (let i = 0; i < position.count; i++) {
        v3.set(normal.getX(i), normal.getY(i), normal.getZ(i));
        v3.setLength(length);
        position.setXYZ(
          i,
          position.getX(i) + v3.x,
          position.getY(i) + v3.y,
          position.getZ(i) + v3.z
        );
      }
      position.needsUpdate = true;
    });
  }
}
